/* This notice must be untouched at all times.

phylogeovizmapfunctions.js    v. 2.2
This file is part of PhyloGeoViz
The latest version is available at
http://www.phylogeoviz.com

Copyright (c) 2007 Yi-Hsin Erica Tsai. All rights reserved.
Created 15.7.2007 by Yi-Hsin Erica Tsai (Web: http://www.duke.edu/~yet2)
Last modified: 20.8.2007

PhyloGeoViz is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

PhyloGeoViz is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Functions included

function log(str) 
	A console log wrapper function.  It first checks if console.log works, if so, writes out to it.

degtorad(degree)
	Given a degree, returns the radian value

sumarray(ar_a)
	Given a 1D array, ar_a, returns the sum of all the array elements.

maxmatrix(matrix, latlng)
	A function that takes a matrix of Glatlngs, matrix, and returns the maximum value in the specified column ("lat" or "lng"), 

minmatrix(matrix, latlng)
	A function that takes a matrix of Glatlngs, matrix, and returns the minimum value in the specified column ("lat" or "lng"), 

rand(n)
	Generates random number from 1 to n, inclusive

byte2hex(n)
	Takes number n from 0-255 and converts to hexadecimal string e.g. 'AA'
	Courtesy Jim Bumgardner of krazydad.com

RGB2Color(r, g, b)
	Takes 3 hexadecimal string color components and concatenates into standard HTML format
	Courtesy Jim Bumgardner of krazydad.com
	
calcVertices(map, c, r, starttheta, endtheta)
	Given a center, c (Glatlng) and a radius, r (px), calculates up to 20 vertices that make up a slice of a circle. starttheta and endtheta are the angles that define the beginning and end of the slice.
	returns an array of latlng's

getrandpopcs(map, n)
	A function that randomly generates Glatlngs to be used as the population localities.  Returns an array of Glatlngs size of numpops.

clearpiedivs(numpops)
	A function that removes the divs left over from the GOverlay.
	is called after the GOverlay is removed

addPieMoveListener(piedivname)
	A function that adds event listeners that register when the pie has been moved and updates the pop_center variable accordingly.  
	piedivname = string id of the div of the pie you want to add the listener to.

drawpie (map, popnum, r_km, c, thetas, colors)
	A function that draws a single pie on the GMap, map.
	map = GMap on which the pie is drawn.
	popnum = number of the population being passed, will be used to name the div.
	r_km = radius in km of the pie
	c = GLatlng of the center of the pie
	thetas = 1D array that contains the angles of the different slices in the pie.
	colors = 1D array of HTML colors (strings) corresponding to each haplotype.

setdefaultradii(np, nh, ph, nb, bd, rs)
	A function that sets the default radius size for each population based on a given set of values.  
	np = numpops, nh = numhaps, ph = array of population haplotype data, nb = num bins (of different pie sizes), bd = array of divisions between bins (length is nb - 1), rs = array of radii of each pop size (length is nb).  
	Returns an array of pop_radii.  One element per population.

calc_radiiinpx(m, nbins, r_km)
	A function that calculates the value in pixels of each radius given the radius in km.  
	m = map element, nbins = number of bins or different sized circles, r_km = array of radii in km (length = nbins).  
	Returns an array of nbins elements of the radii in pixels.

drawdivcircle(divid, r)
	A function that uses Walter Zorn's graphic library to draw circles in a given div (divid) of a given radius, r (in pixels).
	
drawpiesizelegend(nbins, binbounds, radii, legenddivid)
	A function that writes the table and divs responsible for the pie size legend.  
	Takes nbins = the number of bins, binbounds = array of boundary values for bins (length is nbins - 1), radii = array of radii in pixels for how large the pie is in each bin, legenddivid = id of the pie size legend node where the created table and divs attach.

update_pop_centers()
	Uses the global map for the member functions, and updates the values comprising the pop_centers.  Values could have been updated due to dragging.
	Function not used in code anymore.  Bug fixed by adding an event listener to mouseups.

serializedata()
	A function that packages all the appropriate data elements into their form variables to be passed to the next page.

piesize(plusminus)
	A function that increases or decreases the size of the pies as specified by the user.  
	Function takes a string ('+' or '-') as a parameter and alters the global pop_radii variable.  
	The function wipes the overlays and redraws the pies.

serializeediteddata()
	A function that packages all the appropriate data elements into their form variables to be passed to the next page.  Used to transfer data from the edit data page to the viewer.

*/

//A console log wrapper function.  It first checks if console.log works, if so, writes out to it.
function log(str)
{
	if(console  && console.log)
	{
		console.log(str);
	}
}


function degtorad(degree) {
	return degree * Math.PI / 180;
}

function sumarray(ar_a)
{
	var total = 0;
	for (var i = 0; i < ar_a.length; ++i)
	{
		total = total + ar_a[i];
	}
	
	return total;
}

//A function that takes a matrix of Glatlngs, matrix, and returns the maximum value in the specified column ("lat" or "lng"), 
function maxmatrix(matrix, latlng)
{
	var max;

	if (latlng == "lat")
	{
		max = matrix[0].lat();
		for (var r = 1; r < matrix.size(); ++r)
		{
			if (max < matrix[r].lat())
				max = matrix[r].lat();
		}
	}
	
	if (latlng == "lng")
	{
			max = matrix[0].lng();
			for (var r = 1; r < matrix.size(); ++r)
			{
				if (max < matrix[r].lng())
					max = matrix[r].lng();
			}
	}
	
	
	if (max)
		return max;
	else
		return "error";
}

//A function that takes a matrix of Glatlngs, matrix, and returns the minimum value in the specified column ("lat" or "lng"), 
function minmatrix(matrix, latlng)
{
	var min;

	if (latlng == "lat")
	{
		min = matrix[0].lat();
		for (var r = 1; r < matrix.size(); ++r)
		{
			if (min > matrix[r].lat())
				min = matrix[r].lat();
		}
	}
	
	if (latlng == "lng")
	{
			min = matrix[0].lng();
			for (var r = 1; r < matrix.size(); ++r)
			{
				if (min > matrix[r].lng())
					min = matrix[r].lng();
			}
	}
	
	
	if (min)
		return min;
	else
		return "error";
}
	
/** Utility Functions **/
// rand - Generates random number from 1 to n, inclusive
function rand ( n )
{
  return ( Math.floor ( Math.random ( ) * n + 1 ) );
}

// byte2hex - Takes number n from 0-255 and converts to hexadecimal string e.g. 'AA'
// Courtesy Jim Bumgardner of krazydad.com
function byte2Hex(n)
{
	var nybHexString = "0123456789ABCDEF";
 	return String(nybHexString.substr((n >> 4) & 0x0F,1)) + nybHexString.substr(n & 0x0F,1);
}

// RGB2Color - Takes 3 hexadecimal string color components and concatenates into standard HTML format
// Courtesy Jim Bumgardner of krazydad.com
function RGB2Color(r,g,b)
{
	return '#' + byte2Hex(r) + byte2Hex(g) + byte2Hex(b);
}

//calcVertices -- given a center (latlng) and a radius (px), calculates up to 20 vertices that make up a slice of a circle.
//returns an array of latlng's
function calcVertices(map, c, r, starttheta, endtheta) {
	var polyVertices = Array();
	starttheta = Math.round(starttheta/5) * 5;
	endtheta = Math.round(endtheta/5) * 5;

	polyVertices.push(c); //each slice starts and ends with the center
	for (var i = starttheta; i <= endtheta; i = i + 5) {
		var pixelX = map.fromLatLngToDivPixel(c).x + (r * Math.cos(degtorad(i)));
		var pixelY = map.fromLatLngToDivPixel(c).y + (r * Math.sin(degtorad(i)));

		var polyPixel = new GPoint(pixelX,pixelY);
		var polyPoint = map.fromDivPixelToLatLng(polyPixel);
		polyVertices.push(polyPoint);
		}
	polyVertices.push(c);  //close the polygon

	return polyVertices;
}

//getrandpopcs -- a function that randomly generates Glatlngs to be used as
//the population localities.  Returns an array of Glatlngs size of numpops.
function getrandpopcs(map, n)
{
	var mapsize = map.getSize();
	var c_array = new Array();

	for (var i = 0; i < n; ++i)
	{
		c_array.push(new GPoint(rand(mapsize.width), rand(mapsize.height)));
		c_array[i] = map.fromDivPixelToLatLng(c_array[i]);
	}

	return c_array;
}

//clearpiedivs -- a function that removes the divs left over from the GOverlay.
//is called after the GOverlay is removed
function clearpiedivs(numpops)
{
	for (var i = 0; i < numpops; ++i)
	{
		if (pop_include[i])
			$("pop" + i).parentNode.removeChild($("pop" + i));
	}
}

// addPieMoveListener -- function that adds event listeners that register when the pie has been moved and updates the pop_center variable accordingly.  piedivname = string id of the div of the pie you want to add the listener too
function addPieMoveListener(piedivname)
{
	//update pop_center of the pie after pie is dragged
	GEvent.addDomListener($(piedivname), "mouseup", 
		function ()
	{
		var j = Number(this.id.slice(3)); //need to get what pop we're on	
		pop_centers[j] = map.fromLatLngToDivPixel(pop_centers[j]); //convert pop_center to pixel coordinates
		pop_centers[j] = new GPoint(pop_centers[j].x +
						parseInt($("pop" + j).style.left),
						pop_centers[j].y + parseInt($("pop" + j).style.top)); //move to new center
		pop_centers[j] = map.fromDivPixelToLatLng(pop_centers[j]);  //convert pop_center to latlng coordinates
	});
}

//drawpie -- a function that draws a single pie in the center of the map with radius about a quarter of the map width.
//The pie is broken into 4 randomly sized slices.
function drawpie(map, popnum, r_km, c, thetas, colors)
{
	var mapcenter = c;
	var numslices = colors.length;
	var sliceVertices;
	var slices = thetas;

	//convert the radius into pixels appropriate for this zoom level
	var bounds = map.getBounds();
	//get the height of the map window (in lat degrees)
	var mapheight = bounds.getNorthEast().lat() - bounds.getSouthWest().lat();
	var r_lat = r_km / 111.12; //converts to degrees latitude
	var radius = r_lat / mapheight * parseInt($("map").style.height);  //should give appropriate radius in pixels

	//create a population div that contains all the haplotype slices
	var newpopdiv = document.createElement("div");
	newpopdiv.id = "pop" + popnum;

	for (var i = 0; i < numslices; ++i) 
	{
		sliceVertices = calcVertices(map, mapcenter, radius, slices[i], slices[i+1]);
		polyColor = colors[i];
		if($("outline").checked)
			map.addOverlay(new GPolygon(sliceVertices, "#000000", .8,.8, polyColor, .85));
		else
			map.addOverlay(new GPolygon(sliceVertices, polyColor, .8,.8, polyColor, .85));
		var haps = document.getElementsByTagName('svg');

		//label the newly created svg with its appropriate haplotype name
		//nest it within a new div and id
		var hapdiv = document.createElement("div");
		var newhap = haps[haps.length - 1];  //new svg's get added to the end of the array
		newhap.id = "hap"+ i + "pop" + popnum;
		Element.addClassName(hapdiv, "hap" + i);
		Element.addClassName(hapdiv, "pop" + popnum); //all haplotypes in this population have this classname
		Element.addClassName(hapdiv, hapgroups[i]);
		var uberparent = newhap.parentNode; //the current parent node, which is where we want to attach the divs too.

		//if it's the first haplotype of the population, attach the new population div to the parent, if not, it has already been done
		if (i == 0) {
			uberparent.appendChild(newpopdiv);
		}

		var child = uberparent.removeChild(newhap);
		newpopdiv.appendChild(hapdiv); //attaches our created hapdiv to the hierarchy in the right place
		hapdiv.appendChild(child); //reassigns our haplotype svg to live inside the hap div
	}

	new GDraggableObject(newpopdiv);  //makes our population div draggable
	
	addPieMoveListener(newpopdiv.id);
}

//setdefaultradii -- a function that sets the default radius size for each population based on a given set of values.  np = numpops, nh = numhaps, ph = array of population haplotype data, nb = num bins (of different pie sizes), bd = array of divisions between bins (length is nb - 1), rs = array of radii of each pop size (length is nb).  Returns an array of pop_radii.  One element per population
function setdefaultradii(np, nh, ph, nb, bd, rs)
{
	var pradii = new Array();
	
	for (var p = 0; p < np; ++p)
	{
		//first calculate the popsize
		var popsize = 0;
		for (var h = 0; h < nh; ++h)
			popsize = popsize + ph[p][h];
		
		//match appropriate pie size with popsize
		if (popsize == 0)
			var psize = 0;
		else
		{
			var psize = rs[0];
			for (var b = 0; (b < nb - 1) && (popsize > bd[b]); ++b)
				psize = rs[b+1];
		}
		pradii[p] = psize;
	}
	
	return pradii;
}

// calc_radiiinpx -- function that calculates the value in pixels of each radius given the radius in km.  m = map element, nbins = number of bins or different sized circles, r_km = array of radii in km (length = nbins).  Returns an array of nbins elements of the radii in pixels.
function calc_radiiinpx(m, nbins, r_km)
{
	var latlngbounds = m.getBounds();
	var SW = latlngbounds.getSouthWest();
	var SE = new GLatLng(latlngbounds.getSouthWest().lat(), latlngbounds.getNorthEast().lng());
	
	var d_km = SW.distanceFrom(SE) / 1000;
	var width_px = m.getSize().width;
	
	var pxperkm = width_px / d_km;
	
	var r_px = new Array();
	for (var b = 0; b < nbins; ++b)
		r_px[b] = r_km[b] * pxperkm;
	
	return r_px;		
}

// drawdivcircle -- function that uses Walter Zorn's graphic library to draw circles in a given div (divid) of a given radius, r (in pixels).
function drawdivcircle(divid, r)
{
	var circlediv = new jsGraphics(divid);
	circlediv.setStroke(1);
	circlediv.fillEllipse(0, -r, r*2, r*2);
	circlediv.paint();  //must call paint to actually draw the circle on the page
}

// drawpiesizelegend -- function that writes the table and divs responsible for the pie size legend.  Takes nbins = the number of bins, binbounds = array of boundary values for bins (length is nbins - 1), radii = array of radii in pixels for how large the pie is in each bin, legenddivid = id of the pie size legend node where the widgetHead and widgetBody attach.
function drawpiesizelegend(nbins, binbounds, radii, legenddivid)
{
	//if we are redrawing the legend, then first delete the old legend
	if ($("popsizelegendtable"))
		$("popsizelegendtable").parentNode.removeChild($("popsizelegendtable"));
		
	//(from p. 330, Javascript, the definitive guide, O'Reilly press)
	//create the table we'll be returning
	var sizelegendtable = document.createElement("table");
	sizelegendtable.id = "popsizelegendtable";
	
	//create the right number of rows with divs for each bin
	for (var b = 0; b < nbins; ++b)
	{
		//setting up the row structure
		var row = document.createElement("tr");
		var binlabel = document.createElement("td");
		var pradius = document.createElement("td");
		var binlabeldiv = document.createElement("div");
		var pradiusdiv = document.createElement("div");
		binlabeldiv.id = "bin" + b;
		pradiusdiv.id = "rad" + b;
		binlabel.appendChild(binlabeldiv);
		pradius.appendChild(pradiusdiv);
		row.appendChild(binlabel);
		row.appendChild(pradius);
		sizelegendtable.appendChild(row);
		
		//attach sizelegendtable to the legend div
		var divnode = jQuery(".widgetBody", $(legenddivid))[0];
		divnode.appendChild(sizelegendtable);
		
		//filling out row info
		if (b == 0)
			binlabeldiv.innerHTML = "<" + binbounds[b];
		
		else if (b == nbins - 1)
			binlabeldiv.innerHTML = ">" + (binbounds[b - 1] + 1);
		
		else
			binlabeldiv.innerHTML = (binbounds[b - 1] + 1) + "-" + binbounds[b];

		$(pradiusdiv.id).setStyle({position: "relative"});  //this line is here so that the circles aren't just drawn in the upper left hand corner of the window
		
		//resize <td> (so it in turns makes <tr> work) so to fit the pies
		pradius.setStyle({
			height: String(2 * radii[b]) + "px",
			width: String(2 * radii[b]) + "px"
		});
		
		drawdivcircle(pradiusdiv.id, radii[b]);
	}
}

// update_pop_centers -- uses the global map for the member functions.
//updates the values comprising the pop_centers.  Values could have
//been updated due to dragging.
//function not used in code anymore.  Bug fixed by adding an event listener to mouseups.
function update_pop_centers()
{
	for (var i = 0; i < numpops; ++i)
	{
		pop_centers[i] = map.fromLatLngToDivPixel(pop_centers[i]); //convert pop_center to pixel coordinates
		pop_centers[i] = new GPoint(pop_centers[i].x +
							parseInt($("pop" + i).style.left),
							pop_centers[i].y + parseInt($("pop" + i).style.top)); //move to new center
		pop_centers[i] = map.fromDivPixelToLatLng(pop_centers[i]);  //convert pop_center to latlng coordinates
	}
}

//serializeexportdata()
//	A function that packages all the appropriate data elements into their form variables to be passed to the next page.
function serializeexportdata()
{
	$("expnumpops").value = numpops;
	$("expnumhaps").value = numhaps;
	$("exppop_names").value = Object.toJSON(pop_names);
	$("exphap_names").value = Object.toJSON(hap_names);
	$("exppop_centers").value = Object.toJSON(pop_centers);
	$("exppop_haps").value = Object.toJSON(pop_haps);
	$("exphapgroups").value = Object.toJSON(hapgroups);
	$("exppop_include").value = Object.toJSON(pop_include);
	$("exppop_radii").value = Object.toJSON(pop_radii);
	$("exphap_colors").value = Object.toJSON(hap_colors);
	$("exppop_hapthetas").value = Object.toJSON(pop_hapthetas);
	$("outline_checked").value = $("outline").checked;
	//JSON is a serialized object notation that converts objects to appropriate strings that can then be unpacked and parsed using evalJSON()
}

// piesize -- a function that increases or decreases the size of the pies as specified by the user.  Function takes a string ('+' or '-') as a parameter and alters the global pop_radii variable.  The function wipes the overlays and redraws the pies.
function piesize(plusminus)
{
	if(plusminus == '+') //if want to make pies larger
	{
		for (var p = 0; p < numpops; ++p)
		{
			pop_radii[p] = pop_radii[p] * 1.1; //increase pie size by 10%
		}
		
		for (var b = 0; b < numbins; ++b)
			radii_in_km[b] = radii_in_km[b] * 1.1; //adjust legend pie sizes
	}

	else
	{
		for (var p = 0; p < numpops; ++p)
		{
			pop_radii[p] = pop_radii[p] * 0.9; //decrease pie size by 10%
		}
		
		for (var b = 0; b < numbins; ++b)
			radii_in_km[b] = radii_in_km[b] * 0.9;  //adjust legend pie sizes
	}
	
	map.clearOverlays();
	
	clearpiedivs(numpops);

	//draw new pies
	for (var i = 0; i <numpops; ++i)
	{
		if(pop_include[i])
			drawpie(map, i, pop_radii[i], pop_centers[i], pop_hapthetas[i], hap_colors);
	}
	
	//redraw pie size legend
	radii_in_px = calc_radiiinpx(map, numbins, radii_in_km);
	drawpiesizelegend(numbins, bindivs, radii_in_px, "popsizelegend");  
}

//A function that sets the color of all the pie slices of all the haplotypes
//in the group.
//Is called by the onchange event handler
function changeGroupColor()
{
	var grouptochange = uniquegroups[this.field.id.substr(13)];
	var wedgesaffected = $("map").getElementsByClassName(grouptochange);
	var newcolor = this.field.value;
	var associatedbutton = this.field.next();
		
	//take the '?' away from button field
	associatedbutton.innerHTML = "";
	
	wedgesaffected.each(function(i) {
		i.firstChild.firstChild.setAttribute("fill", "#" + newcolor); //resets the fill field buried in the appropriate DOM Element.  
		if(!$("outline").checked)  //if it's not checked, so no outline
			i.firstChild.firstChild.setAttribute("stroke", "#" + newcolor); //resets the outline field buried in the appropriate DOM Element.  
	});
	
	//also must change the values in the haplotype colorboxes
	var colorboxesaffected = jQuery("#" + grouptochange + " .widgetBody .span2"); 
	
	var buttonsaffected = jQuery('button', colorboxesaffected);
	colorboxesaffected= jQuery('input', colorboxesaffected);
	colorboxesaffected.attr("value", newcolor);
	buttonsaffected.css("background-color", "#" + newcolor);
	
	//change hap_colors array
	for (var h = 0; h < numhaps; ++h)
	{
		if (hapgroups[h] == grouptochange)
			hap_colors[h] = "#" + newcolor;
	}	
}

//A function that redraws only the pie slices affected by a color change.
//Is called by the onchange event handler
function changeHapColor()
{
	var haptochange = "hap" + this.field.id.substr(8);
	
	var wedgesaffected = $("map").getElementsByClassName(haptochange);
	
	var newcolor = this.field.value;

	var groupbuttonaffected = jQuery("."+hapgroups[this.field.id.substr(8)], "#legend").filter(".groupheader");
	
	//if the color has changed, reset the group color heading
	if(jQuery("input", groupbuttonaffected).attr("value") != newcolor)
	{
		jQuery("input", groupbuttonaffected).attr("value", "ffffff");
		jQuery("button", groupbuttonaffected).css("background-color", "#ffffff");
		jQuery("button", groupbuttonaffected).css("color", "#000000");
		jQuery("button", groupbuttonaffected).attr("innerHTML", "?");
	}
	
	wedgesaffected.each(function(i) {
		i.firstChild.firstChild.setAttribute("fill", "#" + newcolor); //resets the fill field buried in the appropriate DOM Element.
		if(!$("outline").checked)  //if it's not checked, so no outline
			i.firstChild.firstChild.setAttribute("stroke", "#" + newcolor); //resets the outline field buried in the appropriate DOM Element.  
	});
	
	//change hap_colors array
	hap_colors[Number(this.field.id.substr(8))] = "#" + newcolor;
}

//returns an array with integer elements.  The elements are the indices of the old array.
function getsortarrayorder(arr)
{
	//create tracking array
	var temp_arr = new Array();
	for (var i = 0; i < arr.length; ++i)
	{
		temp_arr[i] = new Array();
		temp_arr[i][0] = arr[i];
		temp_arr[i][1] = i;
	}

	var sorted_array = new Array();
	sorted_array.push(temp_arr.pop());  //add first element

	//sort arr, and keep track with temp_arr
	while(temp_arr.length > 0)
	{
		var tmp = temp_arr.pop();

		var index = 0;

		while (index < sorted_array.length && tmp[0] > sorted_array[index][0])
			index = index + 1;

		if (index < sorted_array.length)
			sorted_array.splice(index, 0, tmp);
		else
			sorted_array.push(tmp);
	}

	var return_array = new Array();
	for (var i = 0; i < sorted_array.length; ++i)
		return_array[i] = sorted_array[i][1];

	return return_array;
}

function sortarray(arr, order)
{
	if (arr.length != order.length)
	{
		log("error, array lengths are different");
		return;
	}

	var sorted = new Array();

	for (var i = 0; i < order.length; ++i)
	{
		sorted[i] = arr[order[i]];
	}

	return sorted;
}

//serializeediteddata()
//	A function that packages all the appropriate data elements into their form variables to be passed to the next page.
function serializeediteddata()
{
	$("numpops").value = numpops;
	$("numhaps").value = numhaps;
	$("pop_names").value = Object.toJSON(pop_names);
	$("hap_names").value = Object.toJSON(hap_names);
	$("pop_lat").value = Object.toJSON(pop_lat);
	$("pop_lng").value = Object.toJSON(pop_lng);
	$("pop_haps").value = Object.toJSON(pop_haps);
	//JSON is a serialized object notation that converts objects to appropriate strings that can then be unpacked and parsed using evalJSON()
	$("hapgroups").value = Object.toJSON(hapgroups);
	$("radii_in_km").value = Object.toJSON(radii_in_km);
	$("set_hap_colors").value = Object.toJSON(set_hap_colors);
	
	var p_include = new Array();
	var h_include = new Array();

	//getting pop_include in the right format to send to next page
	for (var p = 0; p < numpops; ++p)
	{
		//if is checked, value = 1, if unchecked, value = 0
		if ($("p"+p+"is_include").checked == true)
		{
			p_include.push(1);
		}
		else
		{
			p_include.push(0);
		}
	}
	$("pop_include").value = Object.toJSON(p_include);

	//create hap_include array
	for (var h = 0; h < numhaps; ++h)
	{
		//if is checked, value = 1, if unchecked, value = 0
		if ($("h"+h+"is_include").checked == true)
		{
			h_include.push(1);
		}
		else
		{
			h_include.push(0);
		}
	}
	$("hap_include").value = Object.toJSON(h_include);
}

//serializetoeditpage()
//	A function that packages all the appropriate data elements into their form variables to be passed from the viewer page to the edit data page.
function serializetoeditpage()
{
	$("numpops").value = numpops;
	$("full_numhaps").value = full_numhaps;
	$("pop_names").value = Object.toJSON(pop_names);
	$("full_hap_names").value = Object.toJSON(full_hap_names);
	$("full_pop_haps").value = Object.toJSON(full_pop_haps);
	$("full_hapgroups").value = Object.toJSON(full_hapgroups);
	$("pop_include").value = Object.toJSON(pop_include);	
	$("hap_include").value = Object.toJSON(hap_include);
	$("radii_in_km").value = Object.toJSON(radii_in_km);
	$("uniquegroups").value = Object.toJSON(uniquegroups);
	
	var pop_lat = new Array();
	var pop_lng = new Array();
	
	for (var p = 0; p < numpops; ++p)
	{
		pop_lat[p] = pop_centers[p].lat();
		pop_lng[p] = pop_centers[p].lng();
	}
	
	$("pop_lat").value = Object.toJSON(pop_lat);
	$("pop_lng").value = Object.toJSON(pop_lng);

	var set_hap_colors = new Array(); //a recoded hap_colors array
	//the prototype toJSON function doesn't work quite right with key value arrays, so I have to do things a bit weird here
	for (var h = 0; h < numhaps; ++h) 
	{
		set_hap_colors[h] = new Array();
		set_hap_colors[h][0] = hap_names[h];
		set_hap_colors[h][1] = hap_colors[h];
	}
	$("set_hap_colors").value = Object.toJSON(set_hap_colors);
}


//elementid is the id of the div that contains the div with class widgetBody that you want to display/undisplay
function toggleBlock(elementid)
{
	var block = jQuery(".widgetBody", $(elementid))[0];
	var blockstatus = block.getStyle('display');
	var toggle = jQuery(".widgetHead .clickable" , $(elementid))[0];

	if (blockstatus == "none")
	{
		block.setStyle({
			display: "block"
		});
		toggle.innerHTML = toggle.innerHTML.replace(/\+/, "-");
	}
	
	else
	{
		block.setStyle({
			display: "none"
		});
		
		toggle.innerHTML = toggle.innerHTML.replace(/-/, "+");
	}
}

//Displays/undisplays the haplotype rows within the haplotype color legend
function toggleHapBlock(e)
{
	var par = this.parentNode;  //the parent div that contains the widgetHead and widgetBody
	var block = jQuery(".widgetBody", par)[0];
	var blockstatus = block.getStyle('display');
	var toggle = jQuery(".widgetHead > .span1" , par)[0];
	
	if (blockstatus == "none")
	{
		block.setStyle({
			display: "block"
		});
		
		toggle.innerHTML = "-";
	}
	
	else
	{
		block.setStyle({
			display: "none"
		});
		
		toggle.innerHTML = "+";
	}
}

function createHapLegend(legendnode)
{
	//Creating the table elements for each hapgroup
	for (var g = 0; g < numgroups; ++g)
	{
		var newgroupdiv = document.createElement('div');
		var newwigheadnode = document.createElement('div');
		var newwigbodynode = document.createElement('div');
		var span1 = document.createElement('span');
		var span2 = document.createElement('span');
		var span3 = document.createElement('span');
		var newinput = document.createElement('input');
		var newbutton = document.createElement('button');

		//construct legend markup structure
		legendnode.appendChild(newgroupdiv);
		newgroupdiv.appendChild(newwigheadnode);
		newgroupdiv.appendChild(newwigbodynode);
		newwigheadnode.appendChild(span1);
		newwigheadnode.appendChild(span2);
		newwigheadnode.appendChild(span3);
		span2.appendChild(newinput);
		span2.appendChild(newbutton);

		//set ids and names
		newgroupdiv.id = uniquegroups[g];
		span3.id = "hapgroup"+g;
		span3.name = "hapgroup"+g;
		newinput.id = "hapgroupcolor"+g;
		newinput.name = "hapgroupcolor"+g;
		newbutton.id = "groupcolorbox"+g;
		newbutton.name = "groupcolorbox"+g;

		//set other attributes
		newwigheadnode.addClassName("widgetHead");
		newwigheadnode.addClassName("groupheader");
		newwigheadnode.addClassName(uniquegroups[g]);
		newwigbodynode.addClassName("widgetBody");
		newwigbodynode.addClassName("haplotype");
		newwigbodynode.addClassName(uniquegroups[g]);
		span1.addClassName("span1");
		newwigheadnode.addClassName("clickable");
		span2.addClassName("span2");
		span3.addClassName("span3");

		span1.innerHTML = "+";
		span3.innerHTML = uniquegroups[g];
		newinput.type = "hidden"; //should be hidden, text just for debugging purposes
		newinput.value = "ffffff";
		new Control.ColorPicker(newinput.id, { IMAGE_BASE : "javascripts/colorpicker/img/", "swatch" : "groupcolorbox"+(g), "onUpdate" : changeGroupColor  }); //onUpdate allows me to get access to the calling element
		newbutton.addClassName('colorbox');
		newbutton.innerHTML = "?";

		//add the event listener that responds to expanding/minimizing the legend data

		newwigheadnode.addEventListener("click", toggleHapBlock, false);
	}

	//Creating the table elements for each haplotype
	for (var h = 0; h < numhaps; ++h)
	{
		var hapdiv = document.createElement('div');
		var span1 = document.createElement('span');
		var span2 = document.createElement('span');
		var span3 = document.createElement('span');
		var newinput = document.createElement('input');
		var newbutton = document.createElement('button');
		var bodynode = jQuery("."+hapgroups[h], $(hapgroups[h])).filter(".haplotype").filter(".widgetBody")[0];

		//construct legend markup structure
		bodynode.appendChild(hapdiv);
		hapdiv.appendChild(span1);
		hapdiv.appendChild(span2);
		hapdiv.appendChild(span3);
		span2.appendChild(newinput);
		span2.appendChild(newbutton);	

		//set ids and names
		hapdiv.id = hap_names[h];
		hapdiv.name = hap_names[h];
		span3.id = "hapname"+h;
		span3.name = "hapname"+h;
		newinput.id = "hapcolor"+h;
		newinput.name = "hapcolor"+h;
		newbutton.id = "colorbox"+h;
		newbutton.name = "colorbox"+h;

		//set other attributes
		hapdiv.addClassName("haprow");
		span1.addClassName("span1");
		span2.addClassName("span2");
		span3.addClassName("span3");

		span1.innerHTML = "&nbsp;";  //nonbreaking space to force the span to format correctly.  Yes, it's a hack.
		span3.innerHTML = hap_names[h];
		newinput.type = "hidden"; //should be hidden, text just for debugging purposes
		newinput.value = hap_colors[h].substring(1, hap_colors[h].length);
		new Control.ColorPicker(newinput.id, { IMAGE_BASE : "javascripts/colorpicker/img/", "swatch" : "colorbox"+(h), "onUpdate" : changeHapColor});
		newbutton.addClassName('colorbox');
	}
}

function mapsize(plusminus)
{
	var currentdimensions = $("map").getDimensions();
	var leftdistance = parseInt($("seccolumn").getStyle("left").substring(0, $("seccolumn").getStyle("left").length-2));

	if (plusminus == '+')  //enlarge map size
	{		
		$("map").setStyle({
			width: (currentdimensions.width * 1.1) + "px",
			height: (currentdimensions.height * 1.1) + "px"
		});
		
		$("seccolumn").setStyle({
			left: (leftdistance + (currentdimensions.width * 0.1)) + "px"
		});
	}
	
	else //shrink map size
	{
		$("map").setStyle({
			width: (currentdimensions.width * 0.9) + "px",
			height: (currentdimensions.height * 0.9) + "px"
		});
		
		$("seccolumn").setStyle({
			left: (leftdistance - (currentdimensions.width * 0.1)) + "px"
		});

	}
}

function drawoutline(onoff)
{
	var svgsobjs = jQuery("svg > path", map);
	
	if (onoff)  //show outlines
	{
		svgsobjs.attr("stroke", "#000");
	}
	
	else //remove outlines
	{
		svgsobjs.each(function(){
			jQuery(this).attr("stroke", jQuery(this).attr("fill"));
		});
		
	}
}